package com.example.test.dialogfactory;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelKt;
import androidx.lifecycle.ViewModelProvider;
import androidx.paging.LoadState;
import androidx.paging.LoadState.Loading;
import androidx.paging.LoadStateAdapter;
import androidx.paging.Pager;
import androidx.paging.PagingConfig;
import androidx.paging.PagingData;
import androidx.paging.PagingDataAdapter;
import androidx.paging.PagingSource;
import androidx.paging.PagingSource.LoadResult.Error;
import androidx.paging.PagingSource.LoadResult.Page;
import androidx.paging.PagingState;
import androidx.paging.rxjava3.PagingRx;
import androidx.paging.rxjava3.RxPagingSource;
import androidx.recyclerview.widget.ConcatAdapter;
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.ejlchina.okhttps.HTTP;
import com.example.test.dialogfactory.MainActivity51.Footer.FooterHolder;
import com.google.gson.Gson;
import easier.safe.gson.GsonFactory;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.io.Reader;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import kotlin.jvm.functions.Function0;
import kotlinx.coroutines.CoroutineScope;

public class MainActivity51 extends AppCompatActivity {

    private static final String TAG = "MainActivity51";
    private static final String url = "https://api.github.com/search/repositories?sort=stars&q=Android";
    private RecyclerView mRecycler;

    public static void start( Context context ) {

        Intent starter = new Intent( context, MainActivity51.class );
        context.startActivity( starter );
    }

    @Override
    protected void onCreate( Bundle savedInstanceState ) {

        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main51 );

        initView();
        initViewAction();
    }

    private void initViewAction() {

        mRecycler.setLayoutManager( new LinearLayoutManager( this ) );
        Adapter adapter = new Adapter();
        ConcatAdapter concatAdapter = adapter.withLoadStateHeaderAndFooter(
            new Footer( new OnClickListener() {
                @Override
                public void onClick( View v ) {

                }
            } ),
            new Footer( new OnClickListener() {
                @Override
                public void onClick( View v ) {

                    adapter.retry();
                }
            } )
        );
        mRecycler.setAdapter( concatAdapter );

        View51Model viewModel = new ViewModelProvider( this ).get( View51Model.class );
        viewModel.flowable()
            .subscribe( new Consumer< PagingData< Data > >() {
                @Override
                public void accept( PagingData< Data > dataPagingData ) throws Throwable {

                    adapter.submitData( getLifecycle(), dataPagingData );
                }
            }, new Consumer< Throwable >() {
                @Override
                public void accept( Throwable throwable ) throws Throwable {

                }
            } );


    }

    private void test() {

        HTTP http = HTTP.builder()
            .build();

        Observable.just( url )
            .map( new Function< String, List< Data > >() {
                @Override
                public List< Data > apply( String s ) throws Throwable {

                    Reader inputStream = http.sync( url ).addUrlPara( "page", 1 ).addUrlPara( "per_page", 5 ).get()
                        .getBody().toCharStream();
                    Gson singletonGson = GsonFactory.getSingletonGson();
                    Repo repo = singletonGson.fromJson( inputStream, Repo.class );
                    return repo.items;
                }
            } )
            .subscribeOn( Schedulers.io() )
            .observeOn( AndroidSchedulers.mainThread() )
            .subscribe( new Consumer< List< Data > >() {
                @Override
                public void accept( List< Data > o ) throws Throwable {

                    Log.d( TAG, "accept: " + o );
                }
            }, new Consumer< Throwable >() {
                @Override
                public void accept( Throwable throwable ) throws Throwable {

                    Log.d( TAG, "accept: " + throwable );
                }
            } );
    }

    private void initView() {

        mRecycler = (RecyclerView) findViewById( R.id.recycler );
    }

    public static class Data {

        public int id;
        public int stargazers_count;
        public String name;
        public String description;

        @Override
        public String toString() {

            return "Data{" +
                "id=" + id +
                ", stargazers_count=" + stargazers_count +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                '}';
        }

        @Override
        public boolean equals( Object o ) {

            if ( this == o ) {
                return true;
            }
            if ( o == null || getClass() != o.getClass() ) {
                return false;
            }

            Data data = (Data) o;

            if ( id != data.id ) {
                return false;
            }
            if ( stargazers_count != data.stargazers_count ) {
                return false;
            }
            if ( !Objects.equals( name, data.name ) ) {
                return false;
            }
            return Objects.equals( description, data.description );
        }

        @Override
        public int hashCode() {

            int result = id;
            result = 31 * result + stargazers_count;
            result = 31 * result + (name != null ? name.hashCode() : 0);
            result = 31 * result + (description != null ? description.hashCode() : 0);
            return result;
        }
    }

    public static class Repo {

        public List< Data > items;
    }

    public static class View51Model extends ViewModel {

        public Flowable< PagingData< Data > > flowable() {

            CoroutineScope viewModelScope = ViewModelKt.getViewModelScope( this );
            Pager< Integer, Data > pager = new Pager< Integer, Data >(
                new PagingConfig( 10 ),
                null,
                new Function0< PagingSource< Integer, Data > >() {
                    @Override
                    public PagingSource< Integer, Data > invoke() {

                        return new Source();
                    }
                } );

            Flowable< PagingData< Data > > flowable = PagingRx.getFlowable( pager );
            return PagingRx.cachedIn( flowable, viewModelScope );
        }
    }

    public static class Source extends RxPagingSource< Integer, Data > {

        private final HTTP http = HTTP.builder().build();

        @Nullable
        @Override
        public Integer getRefreshKey( @NonNull PagingState< Integer, Data > state ) {

            Integer anchorPosition = state.getAnchorPosition();
            if ( anchorPosition == null ) {
                return null;
            }

            Page< Integer, Data > anchorPage = state.closestPageToPosition( anchorPosition );
            if ( anchorPage == null ) {
                return null;
            }

            Integer prevKey = anchorPage.getPrevKey();
            if ( prevKey != null ) {
                return prevKey + 1;
            }

            Integer nextKey = anchorPage.getNextKey();
            if ( nextKey != null ) {
                return nextKey - 1;
            }

            return null;
        }

        @NonNull
        @Override
        public Single< LoadResult< Integer, Data > > loadSingle( @NonNull LoadParams< Integer > params ) {

            // Start refresh at page 1 if undefined.
            Integer nextPageNumber = params.getKey();
            if ( nextPageNumber == null ) {
                nextPageNumber = 1;
            }
            final int i = nextPageNumber;
            int next = nextPageNumber + 1;

            return Single.just( url )
                .subscribeOn( Schedulers.io() )
                .map( new Function< String, Repo >() {
                    @Override
                    public Repo apply( String s ) throws Throwable {

                        Reader inputStream = http.sync( url )
                            .addUrlPara( "page", i )
                            .addUrlPara( "per_page", 10 )
                            .get()
                            .getBody()
                            .toCharStream();
                        Gson singletonGson = GsonFactory.getSingletonGson();
                        return singletonGson.fromJson( inputStream, Repo.class );
                    }
                } )
                .map( new Function< Repo, LoadResult< Integer, Data > >() {
                    @Override
                    public LoadResult< Integer, Data > apply( Repo repo ) throws Throwable {

                        return new Page< Integer, Data >(
                            repo.items,
                            i > 1 ? i - 1 : null,
                            (repo.items.size() == 0) ? null : next,
                            Page.COUNT_UNDEFINED,
                            Page.COUNT_UNDEFINED
                        );
                    }
                } )
                .onErrorReturn( new Function< Throwable, LoadResult< Integer, Data > >() {
                    @Override
                    public LoadResult< Integer, Data > apply( Throwable throwable ) throws Throwable {

                        return new Error< Integer, Data >( throwable );
                    }
                } )
                .delay( 5, TimeUnit.SECONDS );
        }
    }

    public static class Adapter extends PagingDataAdapter< Data, ViewHolder > {

        public Adapter() {

            super( new Comparator() );
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder( @NonNull ViewGroup parent, int viewType ) {

            View view = LayoutInflater.from( parent.getContext() ).inflate( R.layout.activity_main51_item, parent, false );
            return new ViewHolder( view );
        }

        @Override
        public void onBindViewHolder( @NonNull ViewHolder holder, int position ) {

            Data item = getItem( position );
            if ( item != null ) {
                holder.bind( item );
            }
        }
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        private TextView mNameText;
        private TextView mDescriptionText;
        private TextView mStarCountText;


        public ViewHolder( @NonNull View itemView ) {

            super( itemView );

            mNameText = (TextView) itemView.findViewById( R.id.name_text );
            mDescriptionText = (TextView) itemView.findViewById( R.id.description_text );
            mStarCountText = (TextView) itemView.findViewById( R.id.star_count_text );
        }

        @SuppressLint( "DefaultLocale" )
        private void bind( Data item ) {

            mNameText.setText( item.name );
            mDescriptionText.setText( item.description );
            mStarCountText.setText( String.format( "%d", item.stargazers_count ) );
        }
    }

    public static class Comparator extends ItemCallback< Data > {

        @Override
        public boolean areItemsTheSame( @NonNull Data oldItem, @NonNull Data newItem ) {

            return Objects.equals( oldItem.id, newItem.id );
        }

        @Override
        public boolean areContentsTheSame( @NonNull Data oldItem, @NonNull Data newItem ) {

            return Objects.equals( oldItem, newItem );
        }
    }

    public static class Footer extends LoadStateAdapter< FooterHolder > {

        private OnClickListener mOnClickListener;

        public Footer( OnClickListener onClickListener ) {

            mOnClickListener = onClickListener;
        }

        @Override
        public void onBindViewHolder( @NonNull FooterHolder footerHolder, @NonNull LoadState loadState ) {

            footerHolder.bind( loadState );
        }

        @NonNull
        @Override
        public FooterHolder onCreateViewHolder( @NonNull ViewGroup viewGroup, @NonNull LoadState loadState ) {

            View view = LayoutInflater.from( viewGroup.getContext() ).inflate( R.layout.footer_item, viewGroup, false );
            return new FooterHolder( view, mOnClickListener );
        }

        public static class FooterHolder extends RecyclerView.ViewHolder {

            private ProgressBar mProgressBar;
            private Button mRetryButton;

            public FooterHolder( @NonNull View itemView, OnClickListener clickListener ) {

                super( itemView );

                mProgressBar = (ProgressBar) itemView.findViewById( R.id.progress_bar );
                mRetryButton = (Button) itemView.findViewById( R.id.retry_button );

                mRetryButton.setOnClickListener( clickListener );
            }

            private void bind( LoadState state ) {

                if ( Objects.equals( state, Loading.INSTANCE ) ) {
                    mProgressBar.setVisibility( View.VISIBLE );
                } else {
                    mProgressBar.setVisibility( View.GONE );
                }

                if ( state instanceof LoadState.Error ) {
                    mRetryButton.setVisibility( View.VISIBLE );
                } else {
                    mRetryButton.setVisibility( View.GONE );
                }
            }
        }
    }
}